#include "precompiled.h"
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <stdexcept>

#include "Util.h"
#include "Shader.h"

static char EMPTY_STRING[] = "";

ShaderSource::ShaderSource() {
	Initialize(GL_FRAGMENT_SHADER);
}

ShaderSource::ShaderSource(GLenum type) {
	Initialize(type);
}

void ShaderSource::Initialize(GLenum type) {
	mShaderName = NULL;
	mType = type;
	mSource = EMPTY_STRING;
	mInfoLog = EMPTY_STRING;
	loadGlExtensions();
	glGetError();
	mShaderName = mpglCreateShader(type);
	try {
		Util::verifyGlError();
	} catch(const std::exception &) {
		std::cerr << "Error on line " << __LINE__ << "\n";
		throw;
	}
}

ShaderSource::~ShaderSource() {
	//delete the shader
	if (mShaderName != 0) {
		mpglDeleteShader(mShaderName);
		mShaderName = 0;
	}
	if(mInfoLog != EMPTY_STRING) {
		delete[] mInfoLog;
		mInfoLog = EMPTY_STRING;
	}
	if(mSource != EMPTY_STRING) {
		delete[] mSource;
		mSource = EMPTY_STRING;
	}
}

void ShaderSource::load(const char *filename) {
	//if readEntireFile throws an exception, the state of
	//the object will remain unmodified.
	char *source = Util::readEntireFile(filename);
	const GLchar *shader_sources[] = { source };
	glGetError();	//clear the current error
	mpglShaderSource(mShaderName, 1, shader_sources, 0);
	try {
		try {
			Util::verifyGlError();
		} catch(const std::exception &ex) {
			std::cerr << "Error on line " << __LINE__ << "\n";
			throw ex;
		}
	} catch(const std::exception &ex) {
		//if the new source was not successfully associated with this Shader,
		//delete the memory returned by readEntireFile and leave the source unchanged
		delete[] source;
		throw ex;
	}

	//if the new source was successfully loaded, delete the old one
	if(mSource != EMPTY_STRING) {
		delete[] mSource;
	}
	//and replace it with the new one
	mSource = source;
}

void ShaderSource::compile() {
	mpglCompileShader(mShaderName);
	updateInfoLog();
	GLint status;
	mpglGetShaderiv(mShaderName, GL_COMPILE_STATUS, &status);
	if (status != GL_TRUE) {
		std::ostringstream msg;
		msg << __FILE__ << ":" << __LINE__ << " - ";
		msg << "Failed to compile shader. Call getInfoLog() and getSource() for details.";
		throw std::runtime_error(msg.str());
	}
}

GLuint ShaderSource::getName() const {
	return mShaderName;
}

const char *ShaderSource::getSource() const {
	return mSource;
}

const char *ShaderSource::getInfoLog() const {
	return mInfoLog;
}

void ShaderSource::updateInfoLog() {
	if(mInfoLog != EMPTY_STRING) {
		delete[] mInfoLog;
	}
	GLint info_log_length, log_returned_length;
	mpglGetShaderiv(mShaderName, GL_INFO_LOG_LENGTH, &info_log_length);
	if(info_log_length == 0) {
		mInfoLog = EMPTY_STRING;
		return;
	}
	//info_log_length includes allowance for null termination character
	mInfoLog = new char[info_log_length];
	mpglGetShaderInfoLog(mShaderName, info_log_length, &log_returned_length, mInfoLog);
}

void ShaderSource::loadGlExtensions() {
	mpglCreateShader = (PFNGLCREATESHADERPROC)Util::getGlProc("glCreateShader");
	mpglShaderSource = (PFNGLSHADERSOURCEPROC)Util::getGlProc("glShaderSource");
	mpglCompileShader = (PFNGLCOMPILESHADERPROC)Util::getGlProc("glCompileShader");
	mpglGetShaderiv = (PFNGLGETSHADERIVPROC)Util::getGlProc("glGetShaderiv");
	mpglDeleteShader = (PFNGLDELETESHADERPROC)Util::getGlProc("glDeleteShader");
	mpglGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)Util::getGlProc("glGetShaderInfoLog");
}



/**
 * Begin ShaderProgram definitions.
 */

ShaderProgram::ShaderProgram() :
	mProgramName(0), mInfoLog(EMPTY_STRING) {
	loadGlExtensions();
	glGetError();
	mProgramName = mpglCreateProgram();
	try {
		Util::verifyGlError();
	} catch(const std::exception &ex) {
		std::cerr << "Error on line " << __LINE__ << "\n";
		throw ex;
	}
}

ShaderProgram::~ShaderProgram() {
	if (mProgramName != 0) {
		mpglDeleteProgram(mProgramName);
		mProgramName = 0;
	}

	if (mInfoLog != EMPTY_STRING) {
		delete[] mInfoLog;
		mInfoLog = EMPTY_STRING;
	}
}

void ShaderProgram::attach(ShaderSource *source) {
	glGetError();
	mpglAttachShader(mProgramName, source->getName());
	try {
		Util::verifyGlError();
	} catch(const std::exception &ex) {
		std::cerr << "Error on line " << __LINE__ << "\n";
		throw ex;
	}
}

void ShaderProgram::detach(ShaderSource *source) {
	glGetError();
	mpglDetachShader(mProgramName, source->getName());
	try {
		Util::verifyGlError();
	} catch(const std::exception &ex) {
		std::cerr << "Error on line " << __LINE__ << "\n";
		throw ex;
	}
}

void ShaderProgram::link() {
	mpglLinkProgram(mProgramName);
	GLint status;
	mpglGetProgramiv(mProgramName, GL_LINK_STATUS, &status);
	if (status != GL_TRUE) {
		std::ostringstream msg;
		msg << __FILE__ << ":" << __LINE__ << " - ";
		msg << "Failed to link shader program. Call getInfoLog() and getSource() for details.";
		throw std::runtime_error(msg.str());
	}
}

void ShaderProgram::validate() {
	//validate
	GLint status;
	mpglValidateProgram(mProgramName);
	mpglGetProgramiv(mProgramName, GL_VALIDATE_STATUS, &status);
	if (status != GL_TRUE) {
		std::ostringstream msg;
		msg << __FILE__ << ":" << __LINE__ << " - ";
		msg << "Shader program failed to validate. Call getInfoLog() and getSource() for details.";
		throw std::runtime_error(msg.str());
	}
}

void ShaderProgram::begin() {
	mpglUseProgram(mProgramName);
}

void ShaderProgram::end() {
	mpglUseProgram(0);
}

const char *ShaderProgram::getInfoLog() const {
	return mInfoLog;
}

void ShaderProgram::setUniform(const GLchar *name, GLint value) {
	GLint location = mpglGetUniformLocation(mProgramName, name);
	mpglUniform1i(location, value);
}

void ShaderProgram::setUniform(const GLchar *name, GLfloat v0) {
	GLint location = mpglGetUniformLocation(mProgramName, name);
	mpglUniform1f(location, v0);
}

void ShaderProgram::setUniform(const GLchar *name, GLfloat v0, GLfloat v1) {
	GLint location = mpglGetUniformLocation(mProgramName, name);
	mpglUniform2f(location, v0, v1);
}

void ShaderProgram::setUniform(const GLchar *name, GLfloat v0, GLfloat v1,
		GLfloat v2, GLfloat v3) {
	GLint location = mpglGetUniformLocation(mProgramName, name);
	mpglUniform4f(location, v0, v1, v2, v3);
}

void ShaderProgram::updateInfoLog() {
	if(mInfoLog != EMPTY_STRING) {
		delete[] mInfoLog;
	}
	GLint info_log_length, log_returned_length;
	mpglGetProgramiv(mProgramName, GL_INFO_LOG_LENGTH, &info_log_length);
	if(info_log_length == 0) {
		mInfoLog = EMPTY_STRING;
		return;
	}
	//info_log_length includes allowance for null termination character
	mInfoLog = new char[info_log_length];
	mpglGetProgramInfoLog(mProgramName, info_log_length, &log_returned_length, mInfoLog);
}

void ShaderProgram::loadGlExtensions() {
	mpglCreateProgram = (PFNGLCREATEPROGRAMPROC)Util::getGlProc("glCreateProgram");
	mpglDeleteProgram = (PFNGLDELETEPROGRAMPROC)Util::getGlProc("glDeleteProgram");
	mpglAttachShader = (PFNGLATTACHSHADERPROC)Util::getGlProc("glAttachShader");
	mpglDetachShader = (PFNGLDETACHSHADERPROC)Util::getGlProc("glDetachShader");
	mpglLinkProgram = (PFNGLLINKPROGRAMPROC)Util::getGlProc("glLinkProgram");
	mpglGetProgramiv = (PFNGLGETPROGRAMIVPROC)Util::getGlProc("glGetProgramiv");
	mpglValidateProgram = (PFNGLVALIDATEPROGRAMPROC)Util::getGlProc("glValidateProgram");
	mpglUseProgram = (PFNGLUSEPROGRAMPROC)Util::getGlProc("glUseProgram");
	mpglGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)Util::getGlProc("glGetUniformLocation");
	mpglUniform1i = (PFNGLUNIFORM1IPROC)Util::getGlProc("glUniform1i");
	mpglUniform1f = (PFNGLUNIFORM1FPROC)Util::getGlProc("glUniform1f");
	mpglUniform2f = (PFNGLUNIFORM2FPROC)Util::getGlProc("glUniform2f");
	mpglUniform4f = (PFNGLUNIFORM4FPROC)Util::getGlProc("glUniform4f");
	mpglGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)Util::getGlProc("glGetProgramInfoLog");
}


